// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/heap/scavenge-job.h"

#include "src/base/platform/time.h"
#include "src/heap/gc-tracer.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/heap/spaces.h"
#include "src/isolate.h"
#include "src/v8.h"
#include "src/vm-state-inl.h"

namespace v8 {
namespace internal {

    const double ScavengeJob::kMaxAllocationLimitAsFractionOfNewSpace = 0.8;

    void ScavengeJob::IdleTask::RunInternal(double deadline_in_seconds)
    {
        VMState<GC> state(isolate());
        TRACE_EVENT_CALL_STATS_SCOPED(isolate(), "v8", "V8.Task");
        Heap* heap = isolate()->heap();
        double deadline_in_ms = deadline_in_seconds * static_cast<double>(base::Time::kMillisecondsPerSecond);
        double start_ms = heap->MonotonicallyIncreasingTimeInMs();
        double idle_time_in_ms = deadline_in_ms - start_ms;
        double scavenge_speed_in_bytes_per_ms = heap->tracer()->ScavengeSpeedInBytesPerMillisecond();
        size_t new_space_size = heap->new_space()->Size();
        size_t new_space_capacity = heap->new_space()->Capacity();

        job_->NotifyIdleTask();

        if (ReachedIdleAllocationLimit(scavenge_speed_in_bytes_per_ms, new_space_size,
                new_space_capacity)) {
            if (EnoughIdleTimeForScavenge(
                    idle_time_in_ms, scavenge_speed_in_bytes_per_ms, new_space_size)) {
                heap->CollectGarbage(NEW_SPACE, GarbageCollectionReason::kIdleTask);
            } else {
                // Immediately request another idle task that can get larger idle time.
                job_->RescheduleIdleTask(heap);
            }
        }
    }

    bool ScavengeJob::ReachedIdleAllocationLimit(
        double scavenge_speed_in_bytes_per_ms, size_t new_space_size,
        size_t new_space_capacity)
    {
        if (scavenge_speed_in_bytes_per_ms == 0) {
            scavenge_speed_in_bytes_per_ms = kInitialScavengeSpeedInBytesPerMs;
        }

        // Set the allocation limit to the number of bytes we can scavenge in an
        // average idle task.
        double allocation_limit = kAverageIdleTimeMs * scavenge_speed_in_bytes_per_ms;

        // Keep the limit smaller than the new space capacity.
        allocation_limit = Min<double>(allocation_limit,
            new_space_capacity * kMaxAllocationLimitAsFractionOfNewSpace);
        // Adjust the limit to take into account bytes that will be allocated until
        // the next check and keep the limit large enough to avoid scavenges in tiny
        // new space.
        allocation_limit = Max<double>(allocation_limit - kBytesAllocatedBeforeNextIdleTask,
            kMinAllocationLimit);

        return allocation_limit <= new_space_size;
    }

    bool ScavengeJob::EnoughIdleTimeForScavenge(
        double idle_time_in_ms, double scavenge_speed_in_bytes_per_ms,
        size_t new_space_size)
    {
        if (scavenge_speed_in_bytes_per_ms == 0) {
            scavenge_speed_in_bytes_per_ms = kInitialScavengeSpeedInBytesPerMs;
        }
        return new_space_size <= idle_time_in_ms * scavenge_speed_in_bytes_per_ms;
    }

    void ScavengeJob::RescheduleIdleTask(Heap* heap)
    {
        // Make sure that we don't reschedule more than one time.
        // Otherwise, we might spam the scheduler with idle tasks.
        if (!idle_task_rescheduled_) {
            ScheduleIdleTask(heap);
            idle_task_rescheduled_ = true;
        }
    }

    void ScavengeJob::ScheduleIdleTaskIfNeeded(Heap* heap, int bytes_allocated)
    {
        bytes_allocated_since_the_last_task_ += bytes_allocated;
        if (bytes_allocated_since_the_last_task_ >= static_cast<int>(kBytesAllocatedBeforeNextIdleTask)) {
            ScheduleIdleTask(heap);
            bytes_allocated_since_the_last_task_ = 0;
            idle_task_rescheduled_ = false;
        }
    }

    void ScavengeJob::ScheduleIdleTask(Heap* heap)
    {
        if (!idle_task_pending_ && !heap->IsTearingDown()) {
            v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(heap->isolate());
            if (V8::GetCurrentPlatform()->IdleTasksEnabled(isolate)) {
                idle_task_pending_ = true;
                auto task = base::make_unique<IdleTask>(heap->isolate(), this);
                V8::GetCurrentPlatform()->GetForegroundTaskRunner(isolate)->PostIdleTask(
                    std::move(task));
            }
        }
    }
} // namespace internal
} // namespace v8
